package frame.laxcen.analyze;

import frame.laxcen.exception.IllegalFrameFormatException;
import frame.util.ByteArrayUtil;
import frame.util.ByteUtil;
import frame.util.RegexUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class DataAnalyzer {

    public static byte[] analyze(String input) throws IllegalFrameFormatException {

        String[][] units = RegexUtil.is("\\[([\\s\\S]*?)]")
                .scan(input)
                .map((unit) -> unit.substring(1, unit.length() - 1).split(" "))
                .map(DataAnalyzer::removeInvalidString)
                .toArray(String[][]::new);

        List<byte[]> tmp = new ArrayList<>();

        for(String[] unit : units) tmp.add(byteArrayOfUnit(unit));

        return ByteArrayUtil.flatten(tmp);
    }

    private static String[] removeInvalidString(String[] unit) {

        LinkedList<String> tmp = new LinkedList<>(Arrays.asList(unit));

        tmp.removeIf((str) -> str.isEmpty() || str.startsWith("#"));

        return tmp.toArray(new String[] {});
    }

    private static byte[] byteArrayOfUnit(String[] unit) throws IllegalFrameFormatException {

        switch (unit[0]) {

            case  "N": return nData(unit);

            case  "V": return vData(unit);

            case "NP": return nData(unit);

            case "VP": return vpData(unit);

            default: throw new IllegalFrameFormatException("Unit must begin with N or V or NP or VP.");
        }
    }

    private static byte[] nData(String[] unit) throws IllegalFrameFormatException {

        List<byte[]> tmp = new ArrayList<>();

        for(int i = 1; i < unit.length; ++i) {

            tmp.add(byteArrayOfNumber(unit[i]));
        }

        return ByteArrayUtil.flatten(tmp);
    }

    private static byte[] vData(String[] unit) throws IllegalFrameFormatException {

        List<byte[]> tmp = new ArrayList<>(Arrays.asList(ByteUtil.shortToBytes(unitLength(unit))));

        for(int i = 1; i < unit.length; ++i) {

            tmp.add(byteArrayOfNumber(unit[i]));
        }

        return ByteArrayUtil.flatten(tmp);
    }

    private static byte[] vpData(String[] unit) throws IllegalFrameFormatException {

        List<byte[]> tmp = new ArrayList<>();

        for(int i = 1; i < unit.length; ++i) {

            tmp.add(byteArrayOfNumber(unit[i]));

            if(tmp.size() == 1) tmp.add(ByteUtil.shortToBytes((short) (unitLength(unit) - 2)));
        }

        return ByteArrayUtil.flatten(tmp);
    }

    private static short unitLength(String[] unit) throws IllegalFrameFormatException {

        int length = 0;

        for(String number : unit) {

            if(number.startsWith("0")) length += byteLengthOfNumber(number);
        }

        return (short) length;
    }

    private static int byteLengthOfNumber(String number) throws IllegalFrameFormatException {

        String flag = number.substring(0, 2);

        switch (flag) {

            case "0x": return 4;

            case "0t": return 2;

            case "0b": return 1;

            default: throw new IllegalFrameFormatException("Number must begin with 0x or 0t or 0b.");
        }
    }

    private static byte[] byteArrayOfNumber(String number) throws IllegalFrameFormatException {

        String flag = number.substring(0, 2), value = number.substring(2);

        switch (flag) {

            case "0x": return ByteUtil.intToBytes(Integer.valueOf(value, 16));

            case "0t": return ByteUtil.shortToBytes(Integer.valueOf(value, 16).shortValue());

            case "0b": return ByteArrayUtil.asByteArray(Integer.valueOf(value, 16).byteValue());

            default: throw new IllegalFrameFormatException("Number must begin with 0x or 0t or 0b.");
        }
    }
}
